﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using System.Xml;

namespace Daystate.ConfigurationManager
{
    /// <summary>
    /// This class gives access to configuration.
    /// </summary>
    [XmlInclude(typeof(ConfigListProperty))]
    [Serializable]
    public abstract class ConfigurationClass : IXmlSerializable, IEnumerable<ConfigProperty>
    {
        public abstract void Initialize();

        /// <summary>
        /// Configuration properties.
        /// </summary>
        List<ConfigProperty> properties = new List<ConfigProperty>();

        /// <summary>
        /// Configuration properties. (Casting inside)
        /// </summary>
        public ConfigProperty[] Properties { get { return this.properties.ToArray<ConfigProperty>(); } }

        /// <summary>
        /// Adds property to properties list.
        /// </summary>
        /// <param name="property">Config property to add.</param>
        /// <returns>Adding result.</returns>
        public virtual bool Add(ConfigProperty property)
        {
            // Abort if config property with exactly the same key exists in properties list. 
            foreach (ConfigProperty item in this.properties)
                if (item.Key == property.Key)
                    return false;

            // Otherwise add property to the list
            (this.properties as ICollection<ConfigProperty>).Add(property);
            return true; // Property added successfuly
        }

        /// <summary>
        /// Creates and adds new property to properties list.
        /// </summary>
        /// <param name="type">Configuration property type.</param>
        /// <param name="key">Configuration property key.</param>
        /// <param name="value">Configuration property value.</param>
        /// <returns>Adding result.</returns>
        public virtual bool Add(SupportedTypesEnum type, string key, object value)
        {
            // Create property
            var property = ConfigPropertyFactory.CreateConfigProperty(type, key, value);

            // Add property to properties list
            if (property != null)
                return Add(property);
            return false;
        }

        /// <summary>
        /// Removes property from properties list.
        /// </summary>
        /// <param name="key">Key of property to be removed.</param>
        /// <returns>Removing result</returns>
        public virtual bool Remove(string key)
        {
            int index = -1;
            for (int i = 0; i < this.properties.Count; i++)
                if (properties[i].Key == key)
                    index = i;

            // Abort if item with given key was not found.
            if (index < 0)
                return false;

            // Remove item
            (this.properties as List<ConfigProperty>).RemoveAt(index);
            return true;
        }

        #region ========== Indexer ==========

        /// <summary>
        /// Look whole properties arrray for the property with same name as the key.
        /// If the property with the same name as the key was not found, return null.
        /// </summary>
        /// <param name="key">Configuration property key</param>
        /// <returns>Configuration property value</returns>
        public ConfigProperty this[string key]
        {
            get
            {
                return GetConfigProperty(key);
            }
        }

        #endregion

        /// <summary>
        /// Look whole properties arrray for the property with same name as the key.
        /// If the property with the same name as the key was not found, return null.
        /// </summary>
        /// <param name="key">Configuration property key</param>
        /// <returns>Configuration property value</returns>
        public ConfigProperty GetConfigProperty(string key)
        {
            try
            {
                return properties.First(property => property.Key == key);
            }
            catch (InvalidOperationException)
            {
                return null;
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine(e.Message);
                throw e;
            }
        }

        #region ========== IXmlSerializable ==========

        public System.Xml.Schema.XmlSchema GetSchema() { return null; }

        /// <summary>
        /// ReadXml
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader)
        {
            // Flush config properties list
            this.properties.Clear();

            reader.Read(); // Skip the opening node
            if (reader.MoveToContent() == XmlNodeType.Element)
            {
                reader.Read(); // Skip ahead to next node
                // Start reading ConfigProperties nodes 
                while (reader.MoveToContent() == XmlNodeType.Element)
                {
                    Type configPropertyType = Type.GetType(reader.LocalName);
                    ConfigProperty property = (ConfigProperty)configPropertyType.Assembly.CreateInstance(reader.LocalName);
                    if (property != null)
                    {
                        property.ReadXml(reader);
                        this.properties.Add(property); // Add property to the list
                    }
                    reader.Read(); // Go to the next config property (if exists)
                }
            }
        }


        /// <summary>
        /// WriteXml
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer)
        {
            if (this.properties.Count > 0)
            {
                writer.WriteStartElement(Constants.PROPERTIES_TAG);

                ConfigProperty[] propertiesArray = this.properties.ToArray<ConfigProperty>();

                if (propertiesArray.Length > 0)
                    // Start deserializing config properties
                    foreach (ConfigProperty item in propertiesArray)
                        item.WriteXml(writer);
                else
                    throw new Exception("Exception occured while casting list to array.");

                writer.WriteEndElement();
            }
        }

        #endregion


        #region ========== IEnumerable ==========

        IEnumerator<ConfigProperty> IEnumerable<ConfigProperty>.GetEnumerator()
        {
            return this.properties.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.properties.GetEnumerator();
        }

        #endregion


        class Constants
        {
            /// <summary>
            /// Config properties section tag in XML file.
            /// </summary>
            public const string PROPERTIES_TAG = "ConfigProperties";
        }
    }
}
